Skip to content

Conversation

mavriel
Copy link

@mavriel mavriel commented Sep 11, 2025

Pull Request

Issue

Closes: #9850

Approach

This fix addresses a data consistency issue in distributed database environments where beforeSave trigger validation queries may read from stale read replicas while update operations target the primary database.

Problem

During beforeSave trigger execution, Parse Server performs permission validation by querying the database before applying updates. However:

  • Update operations always target the primary database
  • Validation queries use default database settings, which may route to read replicas
  • In environments with replication lag, validation may fail with "Object not found" errors when the object exists in primary but hasn't propagated to replicas yet

Solution

Modified DatabaseController.update() method to ensure validation queries (validateOnly: true) explicitly use { readPreference: 'primary' }. This guarantees that:

  • Validation reads from the same database that updates will target
  • Eliminates race conditions caused by replication lag
  • Maintains consistency between validation and update operations

Key Changes

  • Added { readPreference: 'primary' } option for validation queries in src/Controllers/DatabaseController.js
  • Only affects internal validation behavior during beforeSave triggers
  • No changes to public APIs or user-facing functionality

Testing

Added comprehensive test coverage in spec/DatabaseController.spec.js:

  • Verifies validateOnly: true queries use primary readPreference
  • Confirms normal updates maintain existing behavior
  • Uses mock storage adapter to validate exact query parameters

Tasks

  • Add tests

Summary by CodeRabbit

  • Bug Fixes

    • Validation-only updates now read from the primary database to avoid stale reads and intermittent "not found" responses. No API or result changes.
  • Tests

    • New tests verify that validation-only operations use primary reads and that full-update flows perform actual update calls (ensuring validation path does not trigger updates).

…eadPreference

- Ensures update validation always reads from primary replica in DB
- Fixes potential data consistency issues in distributed database environments
- Adds comprehensive tests for validateOnly behavior with primary readPreference
Copy link

parse-github-assistant bot commented Sep 11, 2025

🚀 Thanks for opening this pull request!

Copy link

coderabbitai bot commented Sep 11, 2025

📝 Walkthrough

Walkthrough

Changes make DatabaseController.update use a primary read for its internal validation path when validateOnly is true, and add tests that assert primary-read is used for validation and that the normal update path is used when validateOnly is false.

Changes

Cohort / File(s) Summary
Controller: validateOnly read preference
src/Controllers/DatabaseController.js
In the validateOnly branch of update, change the adapter find call options from {} to { readPreference: 'primary' }. No other behavior or signatures changed.
Tests: update with validateOnly
spec/DatabaseController.spec.js
Add tests covering validateOnly=true and validateOnly=false paths. Introduce a mockStorageAdapter with spies for find, findOneAndUpdate, watch, and getAllClasses. Assert that find is called with readPreference: 'primary' and findOneAndUpdate is not called when validating; assert the inverse when not validating.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant DBController as DatabaseController
  participant Adapter as StorageAdapter

  rect rgb(245,250,255)
  note over Client,DBController: validateOnly = true (validation)
  Client->>DBController: update(request, validateOnly=true)
  DBController->>Adapter: find(query, options={ readPreference: 'primary' })
  alt not found
    DBController-->>Client: error OBJECT_NOT_FOUND
  else found
    DBController-->>Client: {}
  end
  end

  rect rgb(250,245,255)
  note over Client,DBController: validateOnly = false (update)
  Client->>DBController: update(request, validateOnly=false)
  DBController->>Adapter: findOneAndUpdate(query, update, options)
  Adapter-->>DBController: result
  DBController-->>Client: result/ack
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "fix: Prevent stale data reads during update validation with primary readPreference" accurately and concisely describes the primary change—adding readPreference: 'primary' to validation queries to prevent stale reads in distributed environments. The title is specific enough that a developer scanning history would understand the core fix without needing additional context. It uses appropriate terminology and avoids vague language.
Linked Issues Check ✅ Passed The pull request directly addresses all primary objectives from issue #9850. The core requirement—enabling validation queries to use primary readPreference to prevent "Object not found" errors due to replication lag—is met by modifying DatabaseController.update() to include readPreference: 'primary' for validateOnly queries. The implementation solves the multi-region deployment issue by ensuring validation reads from the primary database (matching update targets), comprehensive tests verify the new behavior, and no public APIs are changed. The fix is complete and focused.
Out of Scope Changes Check ✅ Passed All code changes are directly in scope and aligned with the objectives of issue #9850. The modification to DatabaseController.update() adds readPreference: 'primary' for validation queries, directly addressing the stated problem. The test additions in spec/DatabaseController.spec.js verify the new validation behavior and confirm normal updates remain unaffected. No changes introduce unrelated functionality, new features, or modifications to public APIs or unrelated areas of the codebase.
Description Check ✅ Passed The pull request description fully satisfies the template requirements by providing a linked issue (Closes: #9850), a detailed Approach section that clearly explains the problem and solution, and marking the "Add tests" task as complete. The description goes beyond the template by providing comprehensive context about multi-region deployments, the specific race condition with replication lag, and how the fix maintains consistency between validation and update operations. All required information is present and well-structured.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59e599b and a25427a.

📒 Files selected for processing (1)
  • src/Controllers/DatabaseController.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/Controllers/DatabaseController.js

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Sep 11, 2025

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/Controllers/DatabaseController.js (1)

596-602: Force-primary read is correct; add limit and projection to cut load

Only existence is needed here. Pass limit: 1 and a minimal projection to reduce I/O without altering semantics.

-              if (validateOnly) {
-                return this.adapter.find(className, schema, query, { readPreference: 'primary' }).then(result => {
+              if (validateOnly) {
+                // Read from primary to avoid replica lag during beforeSave validation (see #9850).
+                return this.adapter.find(
+                  className,
+                  schema,
+                  query,
+                  { readPreference: 'primary', limit: 1, keys: ['objectId'] }
+                ).then(result => {
spec/DatabaseController.spec.js (2)

619-633: Prevent schema cache bleed between tests

Clear the schema cache in this suite to avoid flaky behavior from prior tests.

   describe('update with validateOnly', () => {
+    beforeEach(() => {
+      Config.get(Parse.applicationId).schemaCache.clear();
+    });

655-657: Make assertion resilient to future option additions

Use partial match so the test won’t break if more options (e.g., limit, keys) are passed.

-      expect(findCall.args[3]).toEqual({ readPreference: 'primary' }); // options parameter
+      expect(findCall.args[3]).toEqual(jasmine.objectContaining({ readPreference: 'primary' })); // options parameter
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82fdb0d and 59e599b.

📒 Files selected for processing (2)
  • spec/DatabaseController.spec.js (1 hunks)
  • src/Controllers/DatabaseController.js (1 hunks)
🔇 Additional comments (1)
spec/DatabaseController.spec.js (1)

663-687: LGTM: correctly asserts update path when validateOnly is false

Good coverage ensuring validation read is bypassed and write path is invoked.

@mavriel mavriel changed the title fix: Prevent stale data reads during update validation with primary r… fix: Prevent stale data reads during update validation with primary readPreference Sep 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ability to specify custom readPreference for beforeSave's internal validation

2 participants